Conditions | 108 |
Total Lines | 696 |
Code Lines | 374 |
Lines | 0 |
Ratio | 0 % |
Changes | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like jquery.flot.pie.js ➔ init often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | /* Flot plugin for rendering pie charts. |
||
68 | function init(plot) { |
||
69 | |||
70 | var canvas = null, |
||
71 | target = null, |
||
72 | options = null, |
||
73 | maxRadius = null, |
||
74 | centerLeft = null, |
||
75 | centerTop = null, |
||
76 | processed = false, |
||
77 | ctx = null; |
||
78 | |||
79 | // interactive variables |
||
80 | |||
81 | var highlights = []; |
||
82 | |||
83 | // add hook to determine if pie plugin in enabled, and then perform necessary operations |
||
84 | |||
85 | plot.hooks.processOptions.push(function(plot, options) { |
||
86 | if (options.series.pie.show) { |
||
87 | |||
88 | options.grid.show = false; |
||
89 | |||
90 | // set labels.show |
||
91 | |||
92 | if (options.series.pie.label.show == "auto") { |
||
93 | if (options.legend.show) { |
||
94 | options.series.pie.label.show = false; |
||
95 | } else { |
||
96 | options.series.pie.label.show = true; |
||
97 | } |
||
98 | } |
||
99 | |||
100 | // set radius |
||
101 | |||
102 | if (options.series.pie.radius == "auto") { |
||
103 | if (options.series.pie.label.show) { |
||
104 | options.series.pie.radius = 3/4; |
||
105 | } else { |
||
106 | options.series.pie.radius = 1; |
||
107 | } |
||
108 | } |
||
109 | |||
110 | // ensure sane tilt |
||
111 | |||
112 | if (options.series.pie.tilt > 1) { |
||
113 | options.series.pie.tilt = 1; |
||
114 | } else if (options.series.pie.tilt < 0) { |
||
115 | options.series.pie.tilt = 0; |
||
116 | } |
||
117 | } |
||
118 | }); |
||
119 | |||
120 | plot.hooks.bindEvents.push(function(plot, eventHolder) { |
||
121 | var options = plot.getOptions(); |
||
122 | if (options.series.pie.show) { |
||
123 | if (options.grid.hoverable) { |
||
124 | eventHolder.unbind("mousemove").mousemove(onMouseMove); |
||
125 | } |
||
126 | if (options.grid.clickable) { |
||
127 | eventHolder.unbind("click").click(onClick); |
||
128 | } |
||
129 | } |
||
130 | }); |
||
131 | |||
132 | plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) { |
||
133 | var options = plot.getOptions(); |
||
134 | if (options.series.pie.show) { |
||
135 | processDatapoints(plot, series, data, datapoints); |
||
|
|||
136 | } |
||
137 | }); |
||
138 | |||
139 | plot.hooks.drawOverlay.push(function(plot, octx) { |
||
140 | var options = plot.getOptions(); |
||
141 | if (options.series.pie.show) { |
||
142 | drawOverlay(plot, octx); |
||
143 | } |
||
144 | }); |
||
145 | |||
146 | plot.hooks.draw.push(function(plot, newCtx) { |
||
147 | var options = plot.getOptions(); |
||
148 | if (options.series.pie.show) { |
||
149 | draw(plot, newCtx); |
||
150 | } |
||
151 | }); |
||
152 | |||
153 | function processDatapoints(plot, series, datapoints) { |
||
154 | if (!processed) { |
||
155 | processed = true; |
||
156 | canvas = plot.getCanvas(); |
||
157 | target = $(canvas).parent(); |
||
158 | options = plot.getOptions(); |
||
159 | plot.setData(combine(plot.getData())); |
||
160 | } |
||
161 | } |
||
162 | |||
163 | function combine(data) { |
||
164 | |||
165 | var total = 0, |
||
166 | combined = 0, |
||
167 | numCombined = 0, |
||
168 | color = options.series.pie.combine.color, |
||
169 | newdata = []; |
||
170 | |||
171 | // Fix up the raw data from Flot, ensuring the data is numeric |
||
172 | |||
173 | for (var i = 0; i < data.length; ++i) { |
||
174 | |||
175 | var value = data[i].data; |
||
176 | |||
177 | // If the data is an array, we'll assume that it's a standard |
||
178 | // Flot x-y pair, and are concerned only with the second value. |
||
179 | |||
180 | // Note how we use the original array, rather than creating a |
||
181 | // new one; this is more efficient and preserves any extra data |
||
182 | // that the user may have stored in higher indexes. |
||
183 | |||
184 | if ($.isArray(value) && value.length == 1) { |
||
185 | value = value[0]; |
||
186 | } |
||
187 | |||
188 | if ($.isArray(value)) { |
||
189 | // Equivalent to $.isNumeric() but compatible with jQuery < 1.7 |
||
190 | if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) { |
||
191 | value[1] = +value[1]; |
||
192 | } else { |
||
193 | value[1] = 0; |
||
194 | } |
||
195 | } else if (!isNaN(parseFloat(value)) && isFinite(value)) { |
||
196 | value = [1, +value]; |
||
197 | } else { |
||
198 | value = [1, 0]; |
||
199 | } |
||
200 | |||
201 | data[i].data = [value]; |
||
202 | } |
||
203 | |||
204 | // Sum up all the slices, so we can calculate percentages for each |
||
205 | |||
206 | for (var i = 0; i < data.length; ++i) { |
||
207 | total += data[i].data[0][1]; |
||
208 | } |
||
209 | |||
210 | // Count the number of slices with percentages below the combine |
||
211 | // threshold; if it turns out to be just one, we won't combine. |
||
212 | |||
213 | for (var i = 0; i < data.length; ++i) { |
||
214 | var value = data[i].data[0][1]; |
||
215 | if (value / total <= options.series.pie.combine.threshold) { |
||
216 | combined += value; |
||
217 | numCombined++; |
||
218 | if (!color) { |
||
219 | color = data[i].color; |
||
220 | } |
||
221 | } |
||
222 | } |
||
223 | |||
224 | for (var i = 0; i < data.length; ++i) { |
||
225 | var value = data[i].data[0][1]; |
||
226 | if (numCombined < 2 || value / total > options.series.pie.combine.threshold) { |
||
227 | newdata.push( |
||
228 | $.extend(data[i], { /* extend to allow keeping all other original data values |
||
229 | and using them e.g. in labelFormatter. */ |
||
230 | data: [[1, value]], |
||
231 | color: data[i].color, |
||
232 | label: data[i].label, |
||
233 | angle: value * Math.PI * 2 / total, |
||
234 | percent: value / (total / 100) |
||
235 | }) |
||
236 | ); |
||
237 | } |
||
238 | } |
||
239 | |||
240 | if (numCombined > 1) { |
||
241 | newdata.push({ |
||
242 | data: [[1, combined]], |
||
243 | color: color, |
||
244 | label: options.series.pie.combine.label, |
||
245 | angle: combined * Math.PI * 2 / total, |
||
246 | percent: combined / (total / 100) |
||
247 | }); |
||
248 | } |
||
249 | |||
250 | return newdata; |
||
251 | } |
||
252 | |||
253 | function draw(plot, newCtx) { |
||
254 | |||
255 | if (!target) { |
||
256 | return; // if no series were passed |
||
257 | } |
||
258 | |||
259 | var canvasWidth = plot.getPlaceholder().width(), |
||
260 | canvasHeight = plot.getPlaceholder().height(), |
||
261 | legendWidth = target.children().filter(".legend").children().width() || 0; |
||
262 | |||
263 | ctx = newCtx; |
||
264 | |||
265 | // WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE! |
||
266 | |||
267 | // When combining smaller slices into an 'other' slice, we need to |
||
268 | // add a new series. Since Flot gives plugins no way to modify the |
||
269 | // list of series, the pie plugin uses a hack where the first call |
||
270 | // to processDatapoints results in a call to setData with the new |
||
271 | // list of series, then subsequent processDatapoints do nothing. |
||
272 | |||
273 | // The plugin-global 'processed' flag is used to control this hack; |
||
274 | // it starts out false, and is set to true after the first call to |
||
275 | // processDatapoints. |
||
276 | |||
277 | // Unfortunately this turns future setData calls into no-ops; they |
||
278 | // call processDatapoints, the flag is true, and nothing happens. |
||
279 | |||
280 | // To fix this we'll set the flag back to false here in draw, when |
||
281 | // all series have been processed, so the next sequence of calls to |
||
282 | // processDatapoints once again starts out with a slice-combine. |
||
283 | // This is really a hack; in 0.9 we need to give plugins a proper |
||
284 | // way to modify series before any processing begins. |
||
285 | |||
286 | processed = false; |
||
287 | |||
288 | // calculate maximum radius and center point |
||
289 | |||
290 | maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2; |
||
291 | centerTop = canvasHeight / 2 + options.series.pie.offset.top; |
||
292 | centerLeft = canvasWidth / 2; |
||
293 | |||
294 | if (options.series.pie.offset.left == "auto") { |
||
295 | if (options.legend.position.match("w")) { |
||
296 | centerLeft += legendWidth / 2; |
||
297 | } else { |
||
298 | centerLeft -= legendWidth / 2; |
||
299 | } |
||
300 | if (centerLeft < maxRadius) { |
||
301 | centerLeft = maxRadius; |
||
302 | } else if (centerLeft > canvasWidth - maxRadius) { |
||
303 | centerLeft = canvasWidth - maxRadius; |
||
304 | } |
||
305 | } else { |
||
306 | centerLeft += options.series.pie.offset.left; |
||
307 | } |
||
308 | |||
309 | var slices = plot.getData(), |
||
310 | attempts = 0; |
||
311 | |||
312 | // Keep shrinking the pie's radius until drawPie returns true, |
||
313 | // indicating that all the labels fit, or we try too many times. |
||
314 | |||
315 | do { |
||
316 | if (attempts > 0) { |
||
317 | maxRadius *= REDRAW_SHRINK; |
||
318 | } |
||
319 | attempts += 1; |
||
320 | clear(); |
||
321 | if (options.series.pie.tilt <= 0.8) { |
||
322 | drawShadow(); |
||
323 | } |
||
324 | } while (!drawPie() && attempts < REDRAW_ATTEMPTS) |
||
325 | |||
326 | if (attempts >= REDRAW_ATTEMPTS) { |
||
327 | clear(); |
||
328 | target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>"); |
||
329 | } |
||
330 | |||
331 | if (plot.setSeries && plot.insertLegend) { |
||
332 | plot.setSeries(slices); |
||
333 | plot.insertLegend(); |
||
334 | } |
||
335 | |||
336 | // we're actually done at this point, just defining internal functions at this point |
||
337 | |||
338 | function clear() { |
||
339 | ctx.clearRect(0, 0, canvasWidth, canvasHeight); |
||
340 | target.children().filter(".pieLabel, .pieLabelBackground").remove(); |
||
341 | } |
||
342 | |||
343 | function drawShadow() { |
||
344 | |||
345 | var shadowLeft = options.series.pie.shadow.left; |
||
346 | var shadowTop = options.series.pie.shadow.top; |
||
347 | var edge = 10; |
||
348 | var alpha = options.series.pie.shadow.alpha; |
||
349 | var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; |
||
350 | |||
351 | if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) { |
||
352 | return; // shadow would be outside canvas, so don't draw it |
||
353 | } |
||
354 | |||
355 | ctx.save(); |
||
356 | ctx.translate(shadowLeft,shadowTop); |
||
357 | ctx.globalAlpha = alpha; |
||
358 | ctx.fillStyle = "#000"; |
||
359 | |||
360 | // center and rotate to starting position |
||
361 | |||
362 | ctx.translate(centerLeft,centerTop); |
||
363 | ctx.scale(1, options.series.pie.tilt); |
||
364 | |||
365 | //radius -= edge; |
||
366 | |||
367 | for (var i = 1; i <= edge; i++) { |
||
368 | ctx.beginPath(); |
||
369 | ctx.arc(0, 0, radius, 0, Math.PI * 2, false); |
||
370 | ctx.fill(); |
||
371 | radius -= i; |
||
372 | } |
||
373 | |||
374 | ctx.restore(); |
||
375 | } |
||
376 | |||
377 | function drawPie() { |
||
378 | |||
379 | var startAngle = Math.PI * options.series.pie.startAngle; |
||
380 | var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; |
||
381 | |||
382 | // center and rotate to starting position |
||
383 | |||
384 | ctx.save(); |
||
385 | ctx.translate(centerLeft,centerTop); |
||
386 | ctx.scale(1, options.series.pie.tilt); |
||
387 | //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera |
||
388 | |||
389 | // draw slices |
||
390 | |||
391 | ctx.save(); |
||
392 | var currentAngle = startAngle; |
||
393 | for (var i = 0; i < slices.length; ++i) { |
||
394 | slices[i].startAngle = currentAngle; |
||
395 | drawSlice(slices[i].angle, slices[i].color, true); |
||
396 | } |
||
397 | ctx.restore(); |
||
398 | |||
399 | // draw slice outlines |
||
400 | |||
401 | if (options.series.pie.stroke.width > 0) { |
||
402 | ctx.save(); |
||
403 | ctx.lineWidth = options.series.pie.stroke.width; |
||
404 | currentAngle = startAngle; |
||
405 | for (var i = 0; i < slices.length; ++i) { |
||
406 | drawSlice(slices[i].angle, options.series.pie.stroke.color, false); |
||
407 | } |
||
408 | ctx.restore(); |
||
409 | } |
||
410 | |||
411 | // draw donut hole |
||
412 | |||
413 | drawDonutHole(ctx); |
||
414 | |||
415 | ctx.restore(); |
||
416 | |||
417 | // Draw the labels, returning true if they fit within the plot |
||
418 | |||
419 | if (options.series.pie.label.show) { |
||
420 | return drawLabels(); |
||
421 | } else return true; |
||
422 | |||
423 | function drawSlice(angle, color, fill) { |
||
424 | |||
425 | if (angle <= 0 || isNaN(angle)) { |
||
426 | return; |
||
427 | } |
||
428 | |||
429 | if (fill) { |
||
430 | ctx.fillStyle = color; |
||
431 | } else { |
||
432 | ctx.strokeStyle = color; |
||
433 | ctx.lineJoin = "round"; |
||
434 | } |
||
435 | |||
436 | ctx.beginPath(); |
||
437 | if (Math.abs(angle - Math.PI * 2) > 0.000000001) { |
||
438 | ctx.moveTo(0, 0); // Center of the pie |
||
439 | } |
||
440 | |||
441 | //ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera |
||
442 | ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false); |
||
443 | ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false); |
||
444 | ctx.closePath(); |
||
445 | //ctx.rotate(angle); // This doesn't work properly in Opera |
||
446 | currentAngle += angle; |
||
447 | |||
448 | if (fill) { |
||
449 | ctx.fill(); |
||
450 | } else { |
||
451 | ctx.stroke(); |
||
452 | } |
||
453 | } |
||
454 | |||
455 | function drawLabels() { |
||
456 | |||
457 | var currentAngle = startAngle; |
||
458 | var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius; |
||
459 | |||
460 | for (var i = 0; i < slices.length; ++i) { |
||
461 | if (slices[i].percent >= options.series.pie.label.threshold * 100) { |
||
462 | if (!drawLabel(slices[i], currentAngle, i)) { |
||
463 | return false; |
||
464 | } |
||
465 | } |
||
466 | currentAngle += slices[i].angle; |
||
467 | } |
||
468 | |||
469 | return true; |
||
470 | |||
471 | function drawLabel(slice, startAngle, index) { |
||
472 | |||
473 | if (slice.data[0][1] == 0) { |
||
474 | return true; |
||
475 | } |
||
476 | |||
477 | // format label text |
||
478 | |||
479 | var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter; |
||
480 | |||
481 | if (lf) { |
||
482 | text = lf(slice.label, slice); |
||
483 | } else { |
||
484 | text = slice.label; |
||
485 | } |
||
486 | |||
487 | if (plf) { |
||
488 | text = plf(text, slice); |
||
489 | } |
||
490 | |||
491 | var halfAngle = ((startAngle + slice.angle) + startAngle) / 2; |
||
492 | var x = centerLeft + Math.round(Math.cos(halfAngle) * radius); |
||
493 | var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; |
||
494 | |||
495 | var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>"; |
||
496 | target.append(html); |
||
497 | |||
498 | var label = target.children("#pieLabel" + index); |
||
499 | var labelTop = (y - label.height() / 2); |
||
500 | var labelLeft = (x - label.width() / 2); |
||
501 | |||
502 | label.css("top", labelTop); |
||
503 | label.css("left", labelLeft); |
||
504 | |||
505 | // check to make sure that the label is not outside the canvas |
||
506 | |||
507 | if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) { |
||
508 | return false; |
||
509 | } |
||
510 | |||
511 | if (options.series.pie.label.background.opacity != 0) { |
||
512 | |||
513 | // put in the transparent background separately to avoid blended labels and label boxes |
||
514 | |||
515 | var c = options.series.pie.label.background.color; |
||
516 | |||
517 | if (c == null) { |
||
518 | c = slice.color; |
||
519 | } |
||
520 | |||
521 | var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;"; |
||
522 | $("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>") |
||
523 | .css("opacity", options.series.pie.label.background.opacity) |
||
524 | .insertBefore(label); |
||
525 | } |
||
526 | |||
527 | return true; |
||
528 | } // end individual label function |
||
529 | } // end drawLabels function |
||
530 | } // end drawPie function |
||
531 | } // end draw function |
||
532 | |||
533 | // Placed here because it needs to be accessed from multiple locations |
||
534 | |||
535 | function drawDonutHole(layer) { |
||
536 | if (options.series.pie.innerRadius > 0) { |
||
537 | |||
538 | // subtract the center |
||
539 | |||
540 | layer.save(); |
||
541 | var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius; |
||
542 | layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color |
||
543 | layer.beginPath(); |
||
544 | layer.fillStyle = options.series.pie.stroke.color; |
||
545 | layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); |
||
546 | layer.fill(); |
||
547 | layer.closePath(); |
||
548 | layer.restore(); |
||
549 | |||
550 | // add inner stroke |
||
551 | |||
552 | layer.save(); |
||
553 | layer.beginPath(); |
||
554 | layer.strokeStyle = options.series.pie.stroke.color; |
||
555 | layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); |
||
556 | layer.stroke(); |
||
557 | layer.closePath(); |
||
558 | layer.restore(); |
||
559 | |||
560 | // TODO: add extra shadow inside hole (with a mask) if the pie is tilted. |
||
561 | } |
||
562 | } |
||
563 | |||
564 | //-- Additional Interactive related functions -- |
||
565 | |||
566 | function isPointInPoly(poly, pt) { |
||
567 | for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) |
||
568 | ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1])) |
||
569 | && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) |
||
570 | && (c = !c); |
||
571 | return c; |
||
572 | } |
||
573 | |||
574 | function findNearbySlice(mouseX, mouseY) { |
||
575 | |||
576 | var slices = plot.getData(), |
||
577 | options = plot.getOptions(), |
||
578 | radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius, |
||
579 | x, y; |
||
580 | |||
581 | for (var i = 0; i < slices.length; ++i) { |
||
582 | |||
583 | var s = slices[i]; |
||
584 | |||
585 | if (s.pie.show) { |
||
586 | |||
587 | ctx.save(); |
||
588 | ctx.beginPath(); |
||
589 | ctx.moveTo(0, 0); // Center of the pie |
||
590 | //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here. |
||
591 | ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false); |
||
592 | ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false); |
||
593 | ctx.closePath(); |
||
594 | x = mouseX - centerLeft; |
||
595 | y = mouseY - centerTop; |
||
596 | |||
597 | if (ctx.isPointInPath) { |
||
598 | if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) { |
||
599 | ctx.restore(); |
||
600 | return { |
||
601 | datapoint: [s.percent, s.data], |
||
602 | dataIndex: 0, |
||
603 | series: s, |
||
604 | seriesIndex: i |
||
605 | }; |
||
606 | } |
||
607 | } else { |
||
608 | |||
609 | // excanvas for IE doesn;t support isPointInPath, this is a workaround. |
||
610 | |||
611 | var p1X = radius * Math.cos(s.startAngle), |
||
612 | p1Y = radius * Math.sin(s.startAngle), |
||
613 | p2X = radius * Math.cos(s.startAngle + s.angle / 4), |
||
614 | p2Y = radius * Math.sin(s.startAngle + s.angle / 4), |
||
615 | p3X = radius * Math.cos(s.startAngle + s.angle / 2), |
||
616 | p3Y = radius * Math.sin(s.startAngle + s.angle / 2), |
||
617 | p4X = radius * Math.cos(s.startAngle + s.angle / 1.5), |
||
618 | p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5), |
||
619 | p5X = radius * Math.cos(s.startAngle + s.angle), |
||
620 | p5Y = radius * Math.sin(s.startAngle + s.angle), |
||
621 | arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]], |
||
622 | arrPoint = [x, y]; |
||
623 | |||
624 | // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt? |
||
625 | |||
626 | if (isPointInPoly(arrPoly, arrPoint)) { |
||
627 | ctx.restore(); |
||
628 | return { |
||
629 | datapoint: [s.percent, s.data], |
||
630 | dataIndex: 0, |
||
631 | series: s, |
||
632 | seriesIndex: i |
||
633 | }; |
||
634 | } |
||
635 | } |
||
636 | |||
637 | ctx.restore(); |
||
638 | } |
||
639 | } |
||
640 | |||
641 | return null; |
||
642 | } |
||
643 | |||
644 | function onMouseMove(e) { |
||
645 | triggerClickHoverEvent("plothover", e); |
||
646 | } |
||
647 | |||
648 | function onClick(e) { |
||
649 | triggerClickHoverEvent("plotclick", e); |
||
650 | } |
||
651 | |||
652 | // trigger click or hover event (they send the same parameters so we share their code) |
||
653 | |||
654 | function triggerClickHoverEvent(eventname, e) { |
||
655 | |||
656 | var offset = plot.offset(); |
||
657 | var canvasX = parseInt(e.pageX - offset.left); |
||
658 | var canvasY = parseInt(e.pageY - offset.top); |
||
659 | var item = findNearbySlice(canvasX, canvasY); |
||
660 | |||
661 | if (options.grid.autoHighlight) { |
||
662 | |||
663 | // clear auto-highlights |
||
664 | |||
665 | for (var i = 0; i < highlights.length; ++i) { |
||
666 | var h = highlights[i]; |
||
667 | if (h.auto == eventname && !(item && h.series == item.series)) { |
||
668 | unhighlight(h.series); |
||
669 | } |
||
670 | } |
||
671 | } |
||
672 | |||
673 | // highlight the slice |
||
674 | |||
675 | if (item) { |
||
676 | highlight(item.series, eventname); |
||
677 | } |
||
678 | |||
679 | // trigger any hover bind events |
||
680 | |||
681 | var pos = { pageX: e.pageX, pageY: e.pageY }; |
||
682 | target.trigger(eventname, [pos, item]); |
||
683 | } |
||
684 | |||
685 | function highlight(s, auto) { |
||
686 | //if (typeof s == "number") { |
||
687 | // s = series[s]; |
||
688 | //} |
||
689 | |||
690 | var i = indexOfHighlight(s); |
||
691 | |||
692 | if (i == -1) { |
||
693 | highlights.push({ series: s, auto: auto }); |
||
694 | plot.triggerRedrawOverlay(); |
||
695 | } else if (!auto) { |
||
696 | highlights[i].auto = false; |
||
697 | } |
||
698 | } |
||
699 | |||
700 | function unhighlight(s) { |
||
701 | if (s == null) { |
||
702 | highlights = []; |
||
703 | plot.triggerRedrawOverlay(); |
||
704 | } |
||
705 | |||
706 | //if (typeof s == "number") { |
||
707 | // s = series[s]; |
||
708 | //} |
||
709 | |||
710 | var i = indexOfHighlight(s); |
||
711 | |||
712 | if (i != -1) { |
||
713 | highlights.splice(i, 1); |
||
714 | plot.triggerRedrawOverlay(); |
||
715 | } |
||
716 | } |
||
717 | |||
718 | function indexOfHighlight(s) { |
||
719 | for (var i = 0; i < highlights.length; ++i) { |
||
720 | var h = highlights[i]; |
||
721 | if (h.series == s) |
||
722 | return i; |
||
723 | } |
||
724 | return -1; |
||
725 | } |
||
726 | |||
727 | function drawOverlay(plot, octx) { |
||
728 | |||
729 | var options = plot.getOptions(); |
||
730 | |||
731 | var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; |
||
732 | |||
733 | octx.save(); |
||
734 | octx.translate(centerLeft, centerTop); |
||
735 | octx.scale(1, options.series.pie.tilt); |
||
736 | |||
737 | for (var i = 0; i < highlights.length; ++i) { |
||
738 | drawHighlight(highlights[i].series); |
||
739 | } |
||
740 | |||
741 | drawDonutHole(octx); |
||
742 | |||
743 | octx.restore(); |
||
744 | |||
745 | function drawHighlight(series) { |
||
746 | |||
747 | if (series.angle <= 0 || isNaN(series.angle)) { |
||
748 | return; |
||
749 | } |
||
750 | |||
751 | //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString(); |
||
752 | octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor |
||
753 | octx.beginPath(); |
||
754 | if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) { |
||
755 | octx.moveTo(0, 0); // Center of the pie |
||
756 | } |
||
757 | octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false); |
||
758 | octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false); |
||
759 | octx.closePath(); |
||
760 | octx.fill(); |
||
761 | } |
||
762 | } |
||
763 | } // end init (plugin body) |
||
764 | |||
821 |